package com.sghd.football.module.game.service.core;

import com.sghd.common.socket.core.Message;
import com.sghd.common.utils.array.ArrayHelper;
import com.sghd.common.utils.json.JsonUtils;
import com.sghd.football.module.data.service.DataService;
import com.sghd.football.module.game.facade.model.GameResult;
import com.sghd.football.module.game.facade.model.JoinResult;
import com.sghd.football.module.game.service.config.model.ConfigRoom;
import com.sghd.football.module.game.service.model.*;
import com.sghd.football.net.PushReceiver;
import com.sghd.football.net.SocketEnum;
import com.sghd.football.utils.ManagedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;

import static com.sghd.football.module.game.service.model.Status.NORMAL;

/**
 * \* Created with 王创辉.
 * \* Date: 2018/5/21
 * \* Time: 16:14
 * \* @desc:
 */
public class Desk {
    private Logger logger = LoggerFactory.getLogger(Desk.class);

    /*** 桌子id(序号)*/
    private int id;
    /*** 桌子所在房间*/
    private Room room;
    /*** 当前桌子的所有玩家<玩家id, 玩家>*/
    private Map<Long, Player> players = new HashMap<>();
    /*** 座位索引*/
    private Queue<Integer> indexs = new ConcurrentLinkedDeque<>();
    /*** 当前游戏状态按位表示 {@link Status}*/
    private int state = NORMAL;
    /*** 上一次下注区域索引*/
    private int lastBet = -1;
    /*** 推送接收者*/
    private PushReceiver receiver;
    /*** 配置*/
    private ConfigRoom configRoom;

    private DataService dataService;

    public Desk(int id, Room room, PushReceiver receiver, ConfigRoom configRoom, DataService dataService) {
        this.receiver = receiver;
        this.id = id;
        this.room = room;
        this.configRoom = configRoom;
        this.dataService = dataService;
        int size = configRoom.getDeskPlayer();//桌子座位
        for (int i = 0; i < size; i++) {
            indexs.add(i);//初始化座位
        }
    }

    /** 上一次推送的反分池，防止重复推送(当奖池没有变化时不推送)*/
    private Map<Medal, Long> lastPool = new HashMap<>();

    /**
     * 定时任务<br>
     * 推送奖池，数据
     * @param date
     */
    private void push(Date date) {
        if (hasStatus(Status.OFFLINE)) {//离线
            return;
        }
        room.addTimer(this, (desk, now) -> {
            //推送奖池更新
            ConcurrentMap<Medal, AtomicLong> pool = room.getRewardPool();
            boolean push = pool.keySet().stream().anyMatch(country -> {
                return (lastPool.get(country) == null ? 0l : lastPool.get(country)) != pool.get(country).get();
            });
            if (push) {//
                Map<Integer, Long> body = new HashMap<>();
                pool.forEach((country, val) -> {
                    lastPool.put(country, val.get());
                    body.put(country.ordinal(), val.get());
                });
                Message message = Message.valueOf(SocketEnum.UPDATE_P, body);
                receiver.push(message, players.keySet().toArray());
            }
            push(now);//定时任务
        }, new Date(date.getTime() + configRoom.getPushInterval()));
    }

    /**
     * 旋转
     * @param playerId
     * @param betIdx
     * @return
     */
    public Map<String, Object> operator(long playerId, int betIdx) throws Exception {
        int baseBet = configRoom.getBetArea()[betIdx];
        int totalBet = baseBet * Rule.size();//本局下注总金额
        Player player = players.get(playerId);
        boolean cfree = player.getFreeCount() > 0;//当前是否是免费
        canOp(player, totalBet, cfree);//状态检查
        addStatus(Status.ROTATE);
        //如果当前是免费旋转
        if (cfree) {
            baseBet = configRoom.getBetArea()[this.lastBet];
            totalBet = 0;//免费游戏不扣下注金额
            player.decressFree();//免费游戏次数减一
        } else {
            this.lastBet = betIdx;
        }
        Result result = player.operator(cfree, room, baseBet, this.lastBet, totalBet, configRoom);
        if (result.isFreeGame()) {
            player.setFreeCount(configRoom.getFreeCount());
        }
        if (logger.isDebugEnabled()) {
            player.getChessBoard().print();//打印棋盘
        }
        result.setTotalBet(totalBet);
        double bet = result.getTotalFen() * 1.0 / (baseBet * Rule.size());
        int awardType = ArrayHelper.getRange(bet, configRoom.getRewardRang());//中奖等级
        //统计免费旋转中累计获得的金币
        if (cfree) {
            player.addFreeMoney(result.getTotalFen());
        } else {
            player.setFreeMoney(0);
        }
        //游戏结果
        GameResult gameResult = GameResult.valueOf(player.getFreeCount(), awardType, result,
                player.getMondey(PropType.GOLD) + (result.getTotalFen() - totalBet), player.getFreeMoney());
        receiver.push(Message.valueOf(SocketEnum.OPERATRO, gameResult), playerId);//发送游戏结果
        if (logger.isDebugEnabled()) {
            result.print();//打印结算数据
            logger.debug("游戏结果 :{}", JsonUtils.object2String(result));
        }

//        SettleTask settleTask = new SettleTask(player, result, room.getDataService(), totalBet, receiver);//结算任务
//        room.submit(this, settleTask);//任务队列
        //开始结算
        if (dataService.settle(player, result)) {
            if (!cfree) {//只有普通旋转得分才抽成加入奖池
                room.updatePool(totalBet);//更新奖池和反分池);
            }
            logger.debug("当前金币[{}]", player.getMondey(PropType.GOLD));
            logger.debug("变更金币[{}]", (result.getTotalFen() - result.getTotalBet()));
            player.change(PropType.GOLD, result.getTotalFen() - result.getTotalBet());
            logger.debug("结算后金币[{}]", player.getMondey(PropType.GOLD));
            removeStatus(Status.ROTATE);//旋转(结算完成)，移除状态
        }
        int type = 0;//客户端区别，是小游戏结算，还是旋转结算
        if (result.isBounds()) {//中了小游戏
            receiver.push(Message.valueOf(SocketEnum.COMPLETE, "{\"type\":\"0\"}"), playerId);//先推送旋转结算完成
            doGame(player);//推送小游戏结果
            type = 1;//推送游戏结算完成
        }
        return JsonUtils.string2Map("{\"type\":" + type + "}");
    }

    /**
     * 小游戏
     * @param player
     * @return
     */
    public void doGame(Player player) throws Exception {
        addStatus(Status.MINI_GAME);//小游戏中
        List<Integer> results = Medal.get();//小游戏结果
        Map<Integer, Integer> counter = new HashMap<>();
        for (int i = 0; i < results.size(); i++) {
            Integer country = results.get(i);
            Integer count = counter.get(country) == null ? 1 : counter.get(country) + 1;
            counter.put(country, count);
            if (count == 3) {
                float rate = configRoom.getCountRate()[i - 2];
                long val = 0l;
                room.rewardLock.lock();
                try {
                    AtomicLong pool = room.getRewardPool().get(Medal.values()[country]);
                    long current = pool.get();
                    val = (long) (current * rate);//小游戏得分
                    pool.set(current - val);
                } finally {
                    room.rewardLock.unlock();
                }
                Map<String, Object> body = new HashMap<>();//封装返回结果
                body.put("result", results);
                body.put("money", val);
                body.put("times", i + 1);//每几次翻到3个相同的国家
                receiver.push(Message.valueOf(SocketEnum.MINI_GAME, body),player.getPlayerId());
//                SettleTask settleTask = new SettleTask(player, result, room.getDataService(), 0, receiver);//结算
//                room.submit(this, settleTask);
                //开始结算
                Result result = Result.valueOf(true, (int) val, results);//小游戏结果
                if (dataService.settle(player, result)) {
                    logger.debug("当前金币[{}]", player.getMondey(PropType.GOLD));
                    logger.debug("变更金币[{}]", (result.getTotalFen() - result.getTotalBet()));
                    player.change(PropType.GOLD, result.getTotalFen() - result.getTotalBet());
                    logger.debug("结算后金币[{}]", player.getMondey(PropType.GOLD));
                    removeStatus(Status.MINI_GAME);//小游戏完成(MINI_END), 移除状态
                }
                return;
            }
        }
    }

    /** 是否可以旋转*/
    private void canOp(Player player, int totalBet, boolean cfree) {
        if (hasStatus(Status.ROTATE)) {//如果是旋转中
            throw new ManagedException(Window.SERVER_ROTATE);
        }
        if (hasStatus(Status.MINI_GAME)) {
            throw new ManagedException(Window.PLAYER_MINIGAME);
        }
        if (!cfree && player.getMondey(PropType.GOLD) < totalBet) {
            //TODO throw new ManagedException(DialogService.Window.NOT_ENOUGH_MONEY);//金币不足了
        }
    }


    public void enter(Player player) {
        players.put(player.getPlayerId(), player);
        int seat = indexs.remove();
        Integer[] rowCol = configRoom.getRowCol();
        ChessBoard chessBoard = ChessBoard.valueOf(rowCol[0], rowCol[1]);
        player.enter(seat, id, chessBoard);
        removeStatus(Status.OFFLINE);//移除离线状态
        push(new Date());//开始推送奖池更新
    }

    public JoinResult reconn(Player player) {
        removeStatus(Status.OFFLINE);//移除离线状态
        push(new Date());//开始推送奖池更新
        JoinResult joinResult = JoinResult.valueOf(configRoom.getBetArea(), player.getMondey(PropType.GOLD),
                player.getNickName(), player.getVipLevel(), player.getHeadUrl(), player.getFreeCount(), configRoom.getFreeCount(), room.getRewardPool(), player.getFreeMoney());
        return joinResult;
    }

    public boolean canJoin() {
        return !indexs.isEmpty();
    }

    public int getId() {
        return id;
    }

    /**下桌*/
    public void exit(Player player, boolean rely) {
        if (!hasStatus(Status.OFFLINE)) {
            addStatus(Status.OFFLINE);//添加离线状态
        }
        if (rely) {
            indexs.add(player.getSeat());
            players.remove(player.getPlayerId());
        }
    }

    public boolean isEmpty() {
        return players.isEmpty();
    }

    public Room getRoom() {
        return this.room;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Desk desk = (Desk) o;
        return id == desk.id;
    }

    public void addStatus(int state) {
        if (hasStatus(state)) {
            return;
        }
        this.state |= state;
    }

    public boolean hasStatus(int state) {
        return (this.state & state) == state;
    }

    public void removeStatus(int state) {
        if (hasStatus(state)) {
            this.state ^= state;
        }
    }

    public static void main(String[] args) {
        int state = 0;
        state |= 1;
        System.out.println((state & Status.ROTATE) == Status.ROTATE);
        int awardType = ArrayHelper.getRange(4.0, new Integer[]{0, 3, 8});
        System.out.println(awardType);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

    public int getState() {
        return this.state;
    }
}
